{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "图片的几何变换包括：图片缩放、图片剪切、图片位移、图片镜像和图片仿射变换。\n",
    "\n",
    "位移、旋转和缩放都是通过矩阵的仿射变换来实现的。\n",
    "\n",
    "本质是对图片数据进行矩阵运算操作。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 图片缩放\n",
    "<img src='images/图片缩放.jpg' width=50%>\n",
    "\n",
    "缩放就是调整图片的大小，使用cv2.resize()函数实现图像的缩放。可以按照比例缩放，也可以按照指定的大小缩放：\n",
    "\n",
    "缩放有几种不同的插值（interpolation）方法，在缩小时推荐cv2.INTER_AREA(区域插值)，在扩大时推荐cv2.INTER_CUBIC(双立方插值)和cv2.INTER_LINEAR(线性插值)。默认情况下所有改变图像尺寸大小的操作使用的是插值法都是cv2.INTER_LINEAR。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(547, 730, 3)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 1.load 2.info 3.resize 4.check\n",
    "import cv2\n",
    "img = cv2.imread('image0.jpg',1)\n",
    "imgInfo = img.shape\n",
    "print(imgInfo)\n",
    "height = imgInfo[0] # 图片的行对应高度\n",
    "width = imgInfo[1] # 图片的列对应宽度\n",
    "mode = imgInfo[2]\n",
    "# 缩放分为等比例缩放和非等比例缩放\n",
    "# 等比例缩放就是原图像的长宽乘以相同的系数\n",
    "dstHeight = int(height*0.5)\n",
    "dstWidth = int(width*0.5)\n",
    "\n",
    "dst = cv2.resize(img,(dstWidth,dstHeight))\n",
    "cv2.imshow('image',dst)\n",
    "cv2.waitKey(0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Wall time: 3.99 ms\n"
     ]
    }
   ],
   "source": [
    "# 设置缩放因子和缩放方法\n",
    "res = cv2.resize(img, None, fx=1.5, fy=1.5, interpolation=cv2.INTER_AREA)\n",
    "cv2.imshow('image',res)\n",
    "cv2.waitKey(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "OpenCV提供了四种常见的缩放方法：最近邻插值、双线性插值、像素关系重采样和立方插值，默认使用双线性插值。\n",
    "\n",
    "[图像放缩中最近邻插值和双线性插值的基本原理](https://blog.csdn.net/Andrew659/article/details/4818988)  \n",
    "[三十分钟理解：线性插值，双线性插值算法](https://blog.csdn.net/xbinworld/article/details/65660665)\n",
    "\n",
    "**最近邻插值**：  \n",
    "现在对图像进行缩放，源图像src:10\\*20，目标图像dst:5\\*10，目标图像的每个点都来自源图像，比如目标图像的(1,2)点用源图像的(2,4)点表示。  \n",
    "目标图像上的每个点的位置都可以用(h,w)表示，位置(h,w)对应于源图像的点(newH,newW)，计算公式如下：\n",
    "* newH = h\\*(src 行 / 目标 行)  , newH = 1\\*(10 / 5） = 2\n",
    "* newW = w\\*(src 列 / 目标 列)  , newW = 2\\*(20 / 10）= 4\n",
    "\n",
    "比如目标图像的点(1,2)对应源图像的点(2,4)，计算出来的结果如果是小数，则进行四舍五入取最近的点，这种方法就是最近邻插值法。  \n",
    "双线性插值不是用四舍五入法。\n",
    "\n",
    "\n",
    "**双线性插值**：  \n",
    "<img src=\"images/双线性插值.jpg\" width=\"30%\">\n",
    "上图中四个角分别表示四个像素点的坐标，通过线性插值的计算方法得到目标图像上的点对应于源图像上的点为(15.2, 22.3)，这里先把该点在水平和竖直方向上做投影，得到A1,A2,B1,B2。  \n",
    "A1的纵坐标为15.2，表示A1这个点的值等于0.8乘以点(15,22)加上0.2乘以(16,22)，通过类似的方法也可以得到A2的值。  \n",
    "B1的横坐标为22.3，表示B1这个点的值等于0.7乘以点(15,22)加上0.3乘以(15,23)，通过类似的方法也可以得到B2的值。  \n",
    "**注意：A1离(15,22)的距离只有0.2，说明该点的作用应该大一些，所以要用0.8乘以该点，即距离近的点权重大。**  \n",
    "计算出A1,A2，最终点等于：A1\\*70% + A2\\*30%。  \n",
    "计算出B1,B2，最终点等于：B1\\*80% + B2\\*20%。\n",
    "\n",
    "用简单的方法表示：  \n",
    "对于一个目标像素，通过计算得到在原图像上的的坐标为(i+u,j+v) (其中i、j均为坐标的整数部分，u、v为坐标的小数部分)，则这个像素的值 f(i+u,j+v) 由原图像中坐标为 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1) 的四个像素的值决定，即：  \n",
    "`f(i+u,j+v) = (1-u)(1-v)f(i,j) + u(1-v)f(i+1,j) + (1-u)vf(i,j+1) + uvf(i+1,j+1)`  \n",
    "其中`f(i,j)`表示原图像(i,j)处的的像素值\n",
    "\n",
    "最近邻插值计算量较小，但可能会造成插值生成的图像灰度值不连续，在灰度变化的地方可能出现明显的锯齿状。双线性插值灰度值连续，图像比较平滑。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 最近邻插值的源码实现\n",
    "# 1.info 2.空白目标模版 3.每个xy位置的像素值\n",
    "import cv2\n",
    "import numpy as np\n",
    "img = cv2.imread('image0.jpg',1)\n",
    "imgInfo = img.shape\n",
    "height = imgInfo[0]\n",
    "width = imgInfo[1]\n",
    "\n",
    "dstHeight = int(height/2)\n",
    "dstWidth = int(width/2)\n",
    "dstImage = np.zeros((dstHeight,dstWidth,3), np.uint8) #0-255 \n",
    "\n",
    "for i in range(0, dstHeight): #行\n",
    "    for j in range(0, dstWidth): #列 \n",
    "        iNew = round(i * (height*1.0/dstHeight))\n",
    "        jNew = round(j * (width*1.0/dstWidth))\n",
    "        dstImage[i, j] = img[iNew, jNew]\n",
    "\n",
    "cv2.imshow('dst',dstImage)\n",
    "cv2.waitKey(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 镜像翻转\n",
    "\n",
    "可以用cv2.flip()函数实现镜像翻转。\n",
    "\n",
    "`dst = cv2.flip(img, 1)`\n",
    "\n",
    "其中参数2 = 0：垂直翻转(沿x轴)，参数2 > 0: 水平翻转(沿y轴)，参数2 < 0: 水平垂直翻转。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 图片剪切\n",
    "\n",
    "<img src='images/图片剪切.jpg' width=50%>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# x:(100,300)\n",
    "# y:(100,200)\n",
    "import cv2\n",
    "img = cv2.imread('image0.jpg',1)\n",
    "imgInfo = img.shape\n",
    "dst = img[100:200, 100:300]\n",
    "cv2.imshow('image',dst)\n",
    "cv2.waitKey(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 图片镜像\n",
    "\n",
    "<img src='images/图片镜像.jpg' width=50%>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "img = cv2.imread('image0.jpg', 1)\n",
    "cv2.imshow('src', img)\n",
    "imgInfo = img.shape\n",
    "height = imgInfo[0]\n",
    "width = imgInfo[1]\n",
    "deep = imgInfo[2]\n",
    "# 新图模板\n",
    "newImgInfo = (height*2, width, deep)\n",
    "dst = np.zeros(newImgInfo, np.uint8) # uint8 \n",
    "\n",
    "for i in range(0, height):\n",
    "    for j in range(0, width):\n",
    "        dst[i,j] = img[i,j] # 绘制上半部分\n",
    "        # (x，2*h - y -1)\n",
    "        dst[height*2-i-1, j] = img[i,j] # 绘制下半部分\n",
    "        # (x, y - h) 或 (x, y + h)\n",
    "        # dst[i-height, j] = img[i,j]\n",
    "\n",
    "for i in range(0,width): # 添加分隔线\n",
    "    dst[height,i] = (0,0,255)#BGR\n",
    "cv2.imshow('dst',dst)\n",
    "cv2.waitKey(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 图片位移\n",
    "\n",
    "<img src='images/图片位移.jpg' width=50%>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "img = cv2.imread('image0.jpg',1)\n",
    "cv2.imshow('src',img)\n",
    "imgInfo = img.shape\n",
    "height = imgInfo[0]\n",
    "width = imgInfo[1]\n",
    "\n",
    "# 移位矩阵，水平方向移动100个像素，竖直方向移动200个像素\n",
    "# 数据类型是np.float32\n",
    "matShift = np.float32([[1,0,100],[0,1,200]])#  2行3列\n",
    "dst = cv2.warpAffine(img, matShift, (width,height)) # 1 data 2 mat 3 dst size\n",
    "cv2.imshow('dst', dst)\n",
    "cv2.waitKey(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "仿射变换就是图像的线性变换（旋转、缩放）加上平移操作。\n",
    "\n",
    "```python\n",
    "warpAffine API的实现原理：\n",
    "[[1,0,100],\n",
    " [0,1,200]]\n",
    "把该矩阵拆分为如下两个矩阵：\n",
    "[[1,0],[0,1]]     2*2  A\n",
    "[[100],[200]]     2*1  B\n",
    "\n",
    "输入矩阵[[x],[y]]  2*1  C\n",
    "矩阵A进行线性变换，矩阵B进行平移\n",
    "A*C+B = [[1*x+0*y],[0*x+1*y]]+[[100],[200]]\n",
    "      = [[x+100],[y+200]] # 得到新的x,y，表示变换后的矩阵\n",
    "```\n",
    "\n",
    "变换矩阵的数据类型是np.float32，函数cv2.warpAffine() 的第三个参数是输出图像的大小(宽，高)。\n",
    "\n",
    "通常用2x3矩阵来表示仿射变换。原像素点坐标(x,y)，经过仿射变换后的点的坐标是T，则矩阵仿射变换基本算法原理：\n",
    "\n",
    "<img src='images/仿射变换公式1.png' width=22%>\n",
    "<img src='images/仿射变换公式2.png' width=12%>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# (10,20)->(110,120)\n",
    "import cv2\n",
    "import numpy as np\n",
    "img = cv2.imread('image0.jpg',1)\n",
    "cv2.imshow('src',img)\n",
    "imgInfo = img.shape\n",
    "height = imgInfo[0]\n",
    "width = imgInfo[1]\n",
    "dst = np.zeros(img.shape,np.uint8)\n",
    "\n",
    "for i in range(0, height-50): # 下移50个像素\n",
    "    for j in range(0, width-100): # 右移100个像素\n",
    "        dst[i+50, j+100]=img[i, j]\n",
    "\n",
    "cv2.imshow('image',dst)\n",
    "cv2.waitKey(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 图片缩放"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "利用之前介绍的矩阵移位公式实现图片缩放。\n",
    "```python\n",
    "[[A1 A2 B1],[A3 A4 B2]]\n",
    "[[A1 A2],[A3 A4]]  [[B1],[B2]]\n",
    "newX = A1*x + A2*y + B1\n",
    "newY = A3*x + A4*y + B2\n",
    "比如缩放0.5：x->x*0.5  y->y*0.5\n",
    "如果把A1,A4设置为0.5，A2,B1,A3设置为0，所以 newX = 0.5*x\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "img = cv2.imread('image0.jpg',1)\n",
    "cv2.imshow('src',img)\n",
    "imgInfo = img.shape\n",
    "height = imgInfo[0]\n",
    "width = imgInfo[1]\n",
    "# 缩放矩阵\n",
    "matScale = np.float32([[0.5,0,0],[0,0.5,0]])\n",
    "dst = cv2.warpAffine(img, matScale, (int(width/2),int(height/2)))\n",
    "\n",
    "cv2.imshow('dst',dst)\n",
    "cv2.waitKey(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 仿射变换\n",
    "\n",
    "通过仿射变换任意变换图形，需要源图像和目标图像上分别一一映射的三个点来定义仿射变换。  \n",
    "将图片中的每个像素点按照一定的规律映射到新的位置，通过把原图片上三个点（左上角、左下角和右上角）重新映射到新的位置来实现仿射变换。\n",
    "\n",
    "<img src='images/仿射变换.jpg' width=50%>\n",
    "\n",
    "在仿射变换中，原图中所有平行线在结果图像中同样平行。为创建这个矩阵，需要从原图像中找到三个点以及他们在输出图像中对应的位置，然后cv2.getAffineTransForm()会创建一个2X3的矩阵。最后这个矩阵会被传给函数cv2.warpAffine()。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 69,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "img = cv2.imread('image0.jpg',1)\n",
    "cv2.imshow('src',img)\n",
    "imgInfo = img.shape\n",
    "height = imgInfo[0]\n",
    "width = imgInfo[1]\n",
    "# src 3->dst 3 (左上角 左下角 右上角)，坐标表示(w,h)\n",
    "matSrc = np.float32([[0, 0],[0, height-1],[width-1, 0]])\n",
    "matDst = np.float32([[50, 0],[300, height-200],[width-300, 100]])\n",
    "# 组合\n",
    "matAffine = cv2.getAffineTransform(matSrc, matDst)# 1 src 2 dst\n",
    "dst = cv2.warpAffine(img, matAffine, (width,height))\n",
    "cv2.imshow('dst',dst)\n",
    "cv2.waitKey(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 图片旋转\n",
    "\n",
    "为构建旋转矩阵，OpenCV提供了一个函数`cv2.getRotationMatrix2D`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "img = cv2.imread('image0.jpg',1)\n",
    "cv2.imshow('src',img)\n",
    "imgInfo = img.shape\n",
    "height = imgInfo[0]\n",
    "width = imgInfo[1]\n",
    "# 旋转矩阵：生成图像旋转所需的仿射变换矩阵，2行3列\n",
    "# 1 旋转中心 2 旋转角度 3 缩放因子\n",
    "matRotate = cv2.getRotationMatrix2D((height*0.5,width*0.5), 45, 0.5)\n",
    "#100*100 25 \n",
    "dst = cv2.warpAffine(img,matRotate,(width,height))\n",
    "cv2.imshow('dst',dst)\n",
    "cv2.waitKey(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 透视变换\n",
    "\n",
    "在透视变换中，需要一个3x3变换矩阵。在变换前是直线变换后还是直线。为创建这个矩阵，需要在原图上找到4个点以及它们在输出图像上对应的位置，这四个点中任意三个都不能共线，可以用函数cv2.getPerspectiveTransform()构建，然后这个矩阵传给函数cv2.warpPerspective()。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "img = cv2.imread('image0.jpg',1)\n",
    "h, w, ch = img.shape\n",
    "\n",
    "# (左上角 右上角 左下角 右下角)，坐标表示(w,h)\n",
    "matSrc = np.float32([[0, 0],[w-1, 0],[0, h-1],[w-1, h-1]])\n",
    "matDst = np.float32([[0, 100],[w-1, 100],[100, h-150],[w-100, h-150]])\n",
    "\n",
    "# 生成透视变换矩阵\n",
    "matSpec=cv2.getPerspectiveTransform(matSrc, matDst)\n",
    "# 进行透视变换\n",
    "dst=cv2.warpPerspective(img, matSpec, (w,h))\n",
    "\n",
    "while True:\n",
    "    cv2.imshow('src',img)\n",
    "    cv2.imshow('dst',dst)\n",
    "    k=cv2.waitKey(0)\n",
    "    if k==ord('q'):\n",
    "        break\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 实现鸟瞰，将摄像机的视角转换到和道路平行\n",
    "img = cv2.imread('birdseye.jpg',1)\n",
    "h, w, ch = img.shape\n",
    "\n",
    "# (左上角 右上角 左下角 右下角)，坐标表示(w,h)\n",
    "matSrc = np.float32([[360, 125],[615, 125],[165, 270],[835, 270]])\n",
    "matDst = np.float32([[165, 30],[835, 30],[165, 270],[835, 270]])\n",
    "\n",
    "# 得到透视变换矩阵\n",
    "matSpec=cv2.getPerspectiveTransform(matSrc, matDst)\n",
    "dst=cv2.warpPerspective(img, matSpec, (w,h))\n",
    "\n",
    "while True:\n",
    "    cv2.imshow('src',img)\n",
    "    cv2.imshow('dst',dst)\n",
    "    k=cv2.waitKey(0)\n",
    "    if k==ord('q'):\n",
    "        break\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
